package com.halachawiki.server.data;


import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.halachawiki.shared.ApproxDate;
import com.halachawiki.shared.HalachicPeriods;
import com.halachawiki.shared.PermissionException;
import com.halachawiki.shared.PublicAuthor;

public class Author extends VersionedEntity {
	
	
	private Author(User currentUser, Key id, boolean modify) throws EntityNotFoundException {
		super(currentUser, id, modify);
	}
	
	public static Author getEntity(User currentUser, Key id, boolean modify) throws EntityNotFoundException {
		return new Author(currentUser, id, modify);
	}

	private Author(User currentUser) { // new entity
		super(currentUser);
	}
	
	private Author(User currentUser, Author previousVersion) { // new version
		super(currentUser, previousVersion);
	}
	
	public String getName() {
		return (String) entity.getProperty("Name");
	}
	public void setNameNC(String name) {
		beginTransaction(false);
		entity.setProperty("Name", name);
	}
	public void setName(String name) {
		beginTransaction(true);
		entity.setProperty("Name", name);
		commit();
	}

	@SuppressWarnings("unchecked")
	public List<String> getNicknames() {
		return (List<String>) entity.getProperty("Nicknames");
	}
	public void setNicknamesNC(List<String> nicknames) {
		beginTransaction(false);
		entity.setProperty("Nicknames", nicknames);
	}
	public void setNicknames(List<String> nicknames) {
		beginTransaction(true);
		entity.setProperty("Nicknames", nicknames);
		commit();
	}
	
	public String getDescription() {
		return (String) entity.getProperty("Description");
	}
	public void setDescriptionNC(String description) {
		beginTransaction(false);
		entity.setProperty("Description", description);
	}
	public void setDescription(String description) {
		beginTransaction(true);
		entity.setProperty("Description", description);
		commit();
	}

	public ApproxDate getBirthDate() {
		String s =  (String) entity.getProperty("BirthDate");
		return (ApproxDate) Util.deserialize(s);
	}
	public void setBirthDateNC(ApproxDate birthDate) {
		beginTransaction(false);
		String s = null;
		if (birthDate != null) {
			s = Util.serialize(birthDate);
		}
		entity.setProperty("BirthDate", s);
	}
	public void setBirthDate(ApproxDate birthDate) {
		beginTransaction(true);
		String s = null;
		if (birthDate != null) {
			s = Util.serialize(birthDate);
		}
		entity.setProperty("BirthDate", s);
		commit();
	}

	public ApproxDate getDeathDate() {
		String s = (String) entity.getProperty("DeathDate");
		return (ApproxDate) Util.deserialize(s);
	}
	public void setDeathDateNC(ApproxDate deathDate) {
		beginTransaction(false);
		String s = null;
		if (deathDate != null) {
			s = Util.serialize(deathDate);
		}
		entity.setProperty("DeathDate", s);
	}
	public void setDeathDate(ApproxDate deathDate) {
		beginTransaction(true);
		String s = null;
		if (deathDate != null) {
			s = Util.serialize(deathDate);
		}
		entity.setProperty("DeathDate", s);
		commit();
	}
	
	public HalachicPeriods getPeriod() {
		String period = (String) entity.getProperty("HalachicPeriod");
		if (period == null) { return null; }
		return HalachicPeriods.getPeriod(HalachicPeriods.Period.valueOf(period));
	}
	public void setPeriodNC(HalachicPeriods period) {
		beginTransaction(false);
		String periodString = null;
		if (period != null)
			periodString = period.getPeriod().toString();
		entity.setProperty("HalachicPeriod", periodString);
	}
	public void setPeriod(HalachicPeriods period) {
		beginTransaction(true);
		HalachicPeriods.Period periodEnum = null;
		if (period != null)
			periodEnum = period.getPeriod();
		entity.setProperty("HalachicPeriod", periodEnum.toString());
		commit();
	}
	
	public PublicAuthor getPublicAuthor() {
		PublicAuthor publicAuthor = new PublicAuthor();
		publicAuthor.versionID = Util.serialize(getId());
		publicAuthor.birthDate = getBirthDate();
		publicAuthor.deathDate = getDeathDate();
		publicAuthor.name = getName();
		List<String> nickNames = getNicknames();
		if (nickNames != null && nickNames.size()>0)
			publicAuthor.nickNames = new ArrayList<String>(getNicknames());
		publicAuthor.description = getDescription();
		publicAuthor.period = getPeriod();
		return publicAuthor;
	}
	
	private static boolean isEqual(String s1, String s2) {
		if (s1 == s2) { return true; } // pointer comparison
		if (s1 == null || s2 == null) { return false; }
		return s1.equals(s2);
	}
	private static boolean isEqual(ApproxDate s1, ApproxDate s2) {
		if (s1 == s2) { return true; } // pointer comparison
		if (s1 == null || s2 == null) { return false; }
		return s1.equals(s2);
	}
	private static boolean isEqual(List<String> l1, List<String> l2) {
		if (l1 == l2) { return true; } // pointer comparison
		if (l1 == null || l2 == null) { return false; }
		if (l1.size() != l2.size()) { return false; }
		Iterator<String> i1 = l1.iterator();
		Iterator<String> i2 = l2.iterator();
		while (i1.hasNext()) {
			if (!i1.next().equals(i2.next()))
				return false;
		}
		return true;
	}
	private static boolean isEqual(HalachicPeriods period1, HalachicPeriods period2) {
		if (period1 == period2) { return true; }
		if (period1 == null || period2 == null) { return false; }
		return period1.getPeriod().equals(period2);
	}
	
	@Override
	public boolean equals(Object other) {
		if (other instanceof PublicAuthor) {
			PublicAuthor publicAuthor = (PublicAuthor) other;
			return (isEqual(publicAuthor.birthDate, getBirthDate()) &&
					isEqual(publicAuthor.deathDate, getDeathDate()) &&
					isEqual(publicAuthor.description, getDescription()) &&
					isEqual(publicAuthor.nickNames, getNicknames()) &&
					isEqual(publicAuthor.name, getName()) &&
					isEqual(publicAuthor.period, getPeriod()));
		}
		if (other instanceof Author) {
			Author otherAuthor = (Author) other;
			return getId().equals(otherAuthor.getId());
		}
		return false;
	}
	
	private static Author getBaseVersion(User currentUser, PublicAuthor publicAuthor) throws PermissionException {
		try {
			if (publicAuthor.versionID == null || publicAuthor.versionID.isEmpty()) { return null; }
			Key versionID = (Key) Util.deserialize(publicAuthor.versionID);
			Author author = getEntity(currentUser, versionID, false);
			if (author == null) {
				throw new PermissionException();
			}
			return author;
		} catch (Exception e) {
			throw new PermissionException();
		}		
	}
	public static boolean changed(User currentUser, PublicAuthor publicAuthor) throws PermissionException {
		Author baseVersion = getBaseVersion(currentUser, publicAuthor);
		if (baseVersion == null) return true;
		return baseVersion.equals(publicAuthor);
	}
	
	public static Author getNewAuthor(User currentUser, PublicAuthor publicAuthor) throws PermissionException {
		try {
			Author prevAuthor = getBaseVersion(currentUser, publicAuthor);
			Author author = null;
			if (prevAuthor != null) {
				if (prevAuthor.equals(publicAuthor)) { return prevAuthor; }
				author = new Author(currentUser, prevAuthor);
			} else
				author = new Author(currentUser);
			author.setNicknamesNC(publicAuthor.nickNames);
			author.setNameNC(publicAuthor.name);
			author.setBirthDateNC(publicAuthor.birthDate);
			author.setDeathDateNC(publicAuthor.deathDate);
			author.setDescriptionNC(publicAuthor.description);
			author.setPeriodNC(publicAuthor.period);
			return author;
		} catch (Exception e) {
			throw new PermissionException();
		}
	}

	static Logger logger = Logger.getLogger(Author.class.getSimpleName());

	public static List<Author> getAllLatestVersions(User currentUser)  {
		List<Key> allLatestVersionKeys = LatestVersionMap.getAllLatestVersions("Author");
		List<Author> allLatestVersions = new ArrayList<Author>();
		for (Key key : allLatestVersionKeys) {
			try {
				allLatestVersions.add(getEntity(currentUser, key, false));
			} catch (EntityNotFoundException e) {
				logger.log(Level.SEVERE, "Exception caught", e);
			}
		}
		return allLatestVersions;
	}
	
	@Override
	public String getTable() {
		return "Author";
	}
}
